#pragma once
#include <iostream>
#include <poll.h>
#include "Socket.hpp"
#include "Log.hpp"
#include "Comm.hpp"
#include "InitAddr.hpp"

using namespace SocketModule;
using namespace LogModule;
const int gdefaultfd = -1;

class PollServer
{
	static const int MAX = 1000;

public:
	PollServer(int post)
		: _post(post), _socket(std::make_unique<SocketModule::TcpSocket>()), _running(false)
	{
	}
	~PollServer()
	{
	}
	void Init()
	{
		_socket->BulidTcpserverMethod(_post);
		for (int i = 0; i < MAX; i++)
		{
			_fds[i].fd = gdefaultfd;
			_fds[i].events = 0;
			_fds[i].revents = 0;
		}
		_fds[0].fd = _socket->Fd();
		_fds[0].events |= POLLIN;
		_fds[0].revents = 0;
	}
	void Accept()
	{
		Inetaddr addr;
		int clientfd = _socket->SocketAccept(&addr);
		if (clientfd < 0)
		{
			LOG(LogLevel::ERROR) << "accept error";
			return;
		}
		else
		{
			LOG(LogLevel::DEBUG) << "accept success";
			// 这里链接成功，不能直接进行读取
			// 因为读取是阻塞的，如果没有数据，则会一直阻塞在这里
			// 所以也必须把clientfd加入到select的监控中，然后进行读取
			// 所以我们必须要一个辅助数值
			int pos = -1;
			for (int j = 0; j < MAX; j++)
			{
				if (_fds[j].fd == gdefaultfd)
				{
					pos = j;
					break;
				}
			}
			if (pos == -1)
			{
				// 已经满了
				LOG(LogLevel::ERROR) << "fd_array is full";
				close(clientfd);
			}
			else
			{
				_fds[pos].fd = clientfd;
				_fds[pos].events = POLLIN;
				_fds[pos].revents = 0;
			}
		}
	}
	void Recv(int who)
	{
		char buffer[1024];
		int n = recv(_fds[who].fd, buffer, sizeof(buffer) - 1, 0);
		if (n < 0)
		{
			LOG(LogLevel::DEBUG) << "客户端读取出错, sockfd: " << _fds[who].fd;
			close(_fds[who].fd);
			_fds[who].fd = gdefaultfd;
			_fds[who].events = 0;
			_fds[who].revents = 0;
		}
		else if (n == 0)
		{
			LOG(LogLevel::DEBUG) << "客户端退出, sockfd: " << _fds[who].fd;
			close(_fds[who].fd);
			_fds[who].fd = gdefaultfd;
			_fds[who].events = 0;
			_fds[who].revents = 0;
		}
		else
		{
			buffer[n] = 0;
			std::cout << "client# " << buffer << std::endl;
			// 把消息进行回显
			std::string message = "echo# ";
			message += buffer;
			send(_fds[who].fd, message.c_str(), message.size(), 0);
		}
	}
	void Dispatcher()
	{
		for (int i = 0; i < MAX; i++)
		{
			if (_fds[i].fd == gdefaultfd)
			{
				continue;
			}
			else if (_fds[i].fd == _socket->Fd()) // 有客户端连接
			{
				if (_fds[i].revents & POLLIN)
				{
					// 有客户端连接
					Accept();
				}
			}
			else // 有数据到达
			{
				if (_fds[i].revents & POLLIN)
				{
					// 这里进行读取
					Recv(i);
				}
			}
		}
	}
	void TestFd()
	{
		std::cout << "pollfd: ";
		for (int i = 0; i < MAX; i++)
		{
			if (_fds[i].fd == gdefaultfd)
				continue;
			std::cout << _fds[i].fd << "[" << Events2Str(_fds[i].events) << "] ";
		}
		std::cout << "\n";
	}
	std::string Events2Str(short events)
	{
		std::string s = ((events & POLLIN) ? "EPOLLIN" : "");
		s += ((events & POLLOUT) ? "POLLOUT" : "");
		return s;
	}
	void Start()
	{
		int timeout = -1;
		_running = true;
		while (true)
		{
			// 不能直接建立Accept,因为Accept是阻塞的,如果没有客户端连接,则会一直阻塞在这里
			// 因为是IO，所以为了减少等待时间，需要使用select，进行多路转接
			// 使用select，可以同时监控多个socket，当某个socket有数据到达时，select会返回，然后进行相应的处理
			// 这样就避免了直接Accept，如果没链接要阻塞住，然后再处理的过程，提高了效率

			int n = poll(_fds, MAX, timeout); // 等待连接
			std::cout << "select n: " << n << std::endl;
			if (n < 0)
			{
				LOG(LogLevel::ERROR) << "select error";
				continue;
			}
			else if (n == 0)
			{
				LOG(LogLevel::DEBUG) << "select timeout";
				continue;
			}
			else
			{
				// 有客户端连接,就可以进行Accept
				std::cout << "有事件就绪啦...," << std::endl;
				// 就可以进行链接了
				Dispatcher();
				TestFd();
			}
		}

		_running = false;
	}

private:
	int _post;
	std::unique_ptr<SocketModule::Socket> _socket;
	bool _running;
	struct pollfd _fds[MAX];
};